package com.kou.backend.service.impl.file;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.kou.backend.entity.FileResourceDO;
import com.kou.backend.entity.FileUploadChunkDO;
import com.kou.backend.enums.FileOpsTypeEnum;
import com.kou.backend.error.CommonErrorEnum;
import com.kou.backend.exception.CommonException;
import com.kou.backend.request.file.UploadFileRequestDTO;
import com.kou.backend.util.FileUtils;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.UUID;

/**
 * 本地磁盘文件存储服务实现类
 *
 * @author: kou
 * @since: 2025/6/26 17:27
 */
@Slf4j
@Service("localFileStorageService")
public class LocalFileStorageServiceImpl extends AbstractFileStorageService {

    @Override
    public void saveFile(MultipartFile file, UploadFileRequestDTO requestDTO, String relativePath, String uploader) {
        try {
            Path fullPath = Paths.get(localRootPath, relativePath);
            //创建父目录(如果不存在)
            Files.createDirectories(fullPath.getParent());
            //保存文件
            Files.copy(file.getInputStream(), fullPath, StandardCopyOption.REPLACE_EXISTING);
            log.info("新文件上传成功:{},路径:{}", file.getOriginalFilename(), relativePath);
        } catch (IOException e) {
            log.error("saveFileToLocal error：", e);
            throw new CommonException(CommonErrorEnum.ERROR_UPLOAD_FILE);
        }
    }


    @Override
    public InputStream getFileStream(String fileId) throws IOException {
        FileResourceDO fileResourceDO = fileResourceDao.selectById(fileId);
        if (ObjectUtil.isEmpty(fileResourceDO)) {
            throw new CommonException(CommonErrorEnum.ERROR_FILE_NOT_EXIST);
        }

        Path filePath = Paths.get(localRootPath, fileResourceDO.getStoragePath());
        //KouTODO:2025/6/30 15:27 kou 添加文件访问日志
        return Files.newInputStream(filePath);
    }

    @Override
    public void fileStreamToResponse(String fileId, String type, HttpServletResponse response) throws IOException {
        FileResourceDO fileResourceDO = fileResourceDao.selectById(fileId);
        if (ObjectUtil.isEmpty(fileResourceDO)) {
            throw new CommonException(CommonErrorEnum.ERROR_FILE_NOT_EXIST);
        }

        Path filePath = Paths.get(localRootPath, fileResourceDO.getStoragePath());
        if (FileOpsTypeEnum.PREVIEW.getFileOpsType().equals(type)) {
            //预览header
            FileUtils.previewToResponse(Files.newInputStream(filePath), fileResourceDO.getStoragePath(), response);
        } else if (FileOpsTypeEnum.DOWNLOAD_FILE.getFileOpsType().equals(type)) {
            FileUtils.downloadToResponse(Files.newInputStream(filePath), fileResourceDO.getStoragePath(), response);
        }

        //KouTODO:2025/6/30 15:27 kou 添加文件访问日志
    }

    @Override
    public String generatePresignedUrl(String storagePath, Integer expireSeconds) {
        // 本地盘没预签名概念，直接返回后端的访问接口
        return "/v1/api/file-preview/download?fileId=" + URLEncoder.encode(storagePath, StandardCharsets.UTF_8);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteFile(String userAccount, String uploader) throws IOException {
        deleteUserFileDb(uploader, userAccount);

        Path filePath = Paths.get(localRootPath, userAccount);
        Files.deleteIfExists(filePath);

        //KouTODO:2025/6/30 15:27 kou 添加文件访问日志
    }

    @Override
    public boolean checkFileExists(String fileMd5) {
        // 根据文件MD5判断 file_resource 表中是否已存在
        FileResourceDO resourceDO = fileResourceDao.selectOneByFileMd5(fileMd5, storageType);
        return ObjectUtil.isNotEmpty(resourceDO);
    }

    @Override
    public List<Integer> getUploadedChunks(String fileMd5) {
        // 根据文件MD5查询 op_file_upload_chunk 分片状态表，返回已上传分片序号列表
        // 用于前端断点续传判断哪些分片已上传
        return fileUploadChunkDao.selectList(
                new LambdaQueryWrapper<FileUploadChunkDO>()
                        .eq(FileUploadChunkDO::getFileMd5, fileMd5)
                        .eq(FileUploadChunkDO::getUploaded, true)
        ).stream().map(FileUploadChunkDO::getChunkNumber).toList();
    }

    @Override
    public void uploadChunk(String fileMd5, Integer chunkNumber, MultipartFile chunkFile, String uploader) throws IOException {
        // 分片上传：保存每个分片到临时目录 /temp/{md5}/{partNumber}.part
        // 并记录上传状态到 op_file_upload_chunk 分片状态表
        // 构造临时分片路径
        Path chunkDir = Paths.get(localRootPath, "temp", fileMd5);
        Files.createDirectories(chunkDir);
        Path chunkPath = chunkDir.resolve(chunkNumber + ".part");

        // 保存分片文件，如存在则覆盖
        Files.copy(chunkFile.getInputStream(), chunkPath, StandardCopyOption.REPLACE_EXISTING);
        log.info("保存分片成功，MD5:{}，分片号:{}，路径:{}", fileMd5, chunkNumber, chunkPath);

        // 更新分片状态表（若存在则更新，否则插入）
        // 查询当前分片状态
        FileUploadChunkDO chunkDO = fileUploadChunkDao.selectOne(
                new LambdaQueryWrapper<FileUploadChunkDO>()
                        .eq(FileUploadChunkDO::getFileMd5, fileMd5)
                        .eq(FileUploadChunkDO::getChunkNumber, chunkNumber)
        );
        if (ObjectUtil.isEmpty(chunkDO)) {
            // 不存在则插入新记录
            chunkDO = new FileUploadChunkDO();
            chunkDO.setFileMd5(fileMd5);
            chunkDO.setChunkNumber(chunkNumber);
            chunkDO.setChunkSize(chunkFile.getSize());
            chunkDO.setUploaded(1);
            chunkDO.setUploader(uploader);
            fileUploadChunkDao.insert(chunkDO);
        } else {
            // 已存在则更新上传状态和分片大小
            chunkDO.setUploaded(1);
            chunkDO.setChunkSize(chunkFile.getSize());
            fileUploadChunkDao.updateById(chunkDO);
        }
    }

    @Override
    public String mergeChunks(String fileMd5, String uploader, String originalFileName) throws IOException {
        // 合并分片：从临时目录按序读取分片文件，合并写成最终文件
        String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM"));
        String uuidName = UUID.randomUUID() + "_" + originalFileName;
        String relativePath = Paths.get(uploader, datePath, uuidName).toString().replace("\\", "/");

        Path chunkDir = Paths.get(localRootPath, "temp", fileMd5);
        Path mergedFilePath = Paths.get(localRootPath, relativePath);
        Files.createDirectories(mergedFilePath.getParent());

        // 按序合并分片（这里示例假设分片号连续）
        try (var outputStream = Files.newOutputStream(mergedFilePath, StandardOpenOption.CREATE)) {
            int chunkNumber = 1;
            while (true) {
                Path chunkPath = chunkDir.resolve(chunkNumber + ".part");
                if (!Files.exists(chunkPath)) {
                    break; // 没有更多分片，结束循环
                }
                Files.copy(chunkPath, outputStream);
                chunkNumber++;
            }
        }

        // 合并完成后，可删除临时分片目录
        FileUtils.deleteDirectory(chunkDir.toFile());
        log.info("合并分片完成，MD5:{}，生成文件路径:{}", fileMd5, mergedFilePath);

        //KouTODO:2025/7/4 17:08 kou 写入file_resource 表和 user_file 映射表
        return relativePath;
    }

    @Override
    public FileResourceDO getFileResourceDO(String fileId) {
        return fileResourceDao.selectById(fileId);
    }
}
